home *** CD-ROM | disk | FTP | other *** search
/ Turnbull China Bikeride / Turnbull China Bikeride - Disc 1.iso / DEMON / RISCOS2 / TCP_131S.ARC / c / ax25cmd < prev    next >
Text File  |  1994-03-05  |  16KB  |  691 lines

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <time.h>
  5. #include "global.h"
  6. #include "mbuf.h"
  7. #include "ax25.h"
  8. #include "ax_mbx.h"
  9. #include "timer.h"
  10. #include "iface.h"
  11. #include "lapb.h"
  12. #include "cmdparse.h"
  13. #include "session.h"
  14. #include "netuser.h"
  15. #include "misc.h"
  16.  
  17. static int doaxreset(int, char **);
  18. static int doaxstat(int, char **);
  19. static void dumpstat(struct ax25_cb *);
  20. static int domycall(int, char **);
  21. static int dodigipeat(int, char **);
  22. static int dot1(int, char **);
  23. static int dot2(int, char **);
  24. static int dot3(int, char **);
  25. static int dot4(int, char **);
  26. static int don2(int, char **);
  27. static int domaxframe(int, char **);
  28. static int doheard(int, char **);
  29. static int dopaclen(int, char **);
  30. static int dopthresh(int, char **);
  31. static int doaxwindow(int, char **);
  32.  
  33. static int mh_compare(const void *, const void *);
  34.  
  35. char *ax25states[] = {
  36.   "Disconnected",
  37.   "Conn pending",
  38.   "Disc pending",
  39.   "Connected",
  40.   "Recovery",
  41.   "Frame Reject",
  42. };
  43.  
  44. static struct cmds axcmds[] = {
  45.   "digipeat",     dodigipeat,     0, NULLCHAR,    NULLCHAR,
  46.   "heard",        doheard,        0, NULLCHAR,    NULLCHAR,
  47.   "maxframe",     domaxframe,     0, NULLCHAR,    NULLCHAR,
  48.   "mycall",       domycall,       0, NULLCHAR,    NULLCHAR,
  49.   "paclen",       dopaclen,       0, NULLCHAR,    NULLCHAR,
  50.   "pthresh",      dopthresh,      0, NULLCHAR,    NULLCHAR,
  51.   "reset",        doaxreset,      2, "ax25 reset <axcb>", NULLCHAR,
  52.   "retry",        don2,           0, NULLCHAR,    NULLCHAR,
  53.   "status",       doaxstat,       0, NULLCHAR,    NULLCHAR,
  54.   "t1",           dot1,           0, NULLCHAR,    NULLCHAR,
  55.   "t2",           dot2,           0, NULLCHAR,    NULLCHAR,
  56.   "t3",           dot3,           0, NULLCHAR,    NULLCHAR,
  57.   "t4",           dot4,           0, NULLCHAR,    NULLCHAR,
  58.   "window",       doaxwindow,     0, NULLCHAR,    NULLCHAR,
  59.   NULLCHAR,       NULLFP,         0, "ax25 subcommands: digipeat heard maxframe mycall pthresh paclen reset retry\n                  status t1 t2 t3 t4 window", NULLCHAR,
  60. };
  61.  
  62. /* Multiplexer for top-level ax25 command */
  63. int doax25(int argc, char **argv)
  64. {
  65.   return subcmd(axcmds,argc,argv);
  66. }
  67.  
  68. static int doaxreset(int argc, char **argv)
  69. {
  70.   struct ax25_cb *axp;
  71.   extern char notval[];
  72.  
  73.   argc = argc;
  74.  
  75.   axp = (struct ax25_cb *)htol(argv[1]);
  76.   if(!ax25val(axp))
  77.   {
  78.     cwprintf(NULL, notval);
  79.     return 1;
  80.   }
  81.   reset_ax25(axp);
  82.   return 0;
  83. }
  84.  
  85. /* Display AX.25 link level control blocks */
  86. static int doaxstat(int argc, char **argv)
  87. {
  88.   register int i;
  89.   register struct ax25_cb *axp;
  90.   char tmp[10];
  91.   extern char notval[];
  92.  
  93.   if(argc < 2)
  94.   {
  95.     cwprintf(NULL, "    &AXB IF   Snd-Q   Rcv-Q   Remote    State\r\n");
  96.     for(i=0;i<NHASH;i++)
  97.     {
  98.       for(axp = ax25_cb[i];axp != NULLAX25; axp = axp->next)
  99.       {
  100.         pax25(tmp,&axp->addr.dest);
  101.         cwprintf(NULL, "%8lx %-5s%-8d%-8d%-10s%s\r\n",
  102.         (long)axp,axp->interface->name,
  103.         len_q(axp->txq),len_mbuf(axp->rxq),
  104.         tmp,ax25states[axp->state]);
  105.       }
  106.     }
  107.     return 0;
  108.   }
  109.   axp = (struct ax25_cb *)htol(argv[1]);
  110.   if(!ax25val(axp))
  111.   {
  112.     cwprintf(NULL, notval);
  113.     return 1;
  114.   }
  115.   dumpstat(axp);
  116.   return 0;
  117. }
  118. /* Dump one control block */
  119. static void dumpstat(register struct ax25_cb *axp)
  120. {
  121.   char tmp[10];
  122.   int i;
  123.  
  124.   if(axp == NULLAX25 || axp->interface == NULLIF)
  125.     return;
  126.   cwprintf(NULL, "&AXB  IF   Remote   RB V(S) V(R) Unack P Retry State\r\n");
  127.   pax25(tmp,&axp->addr.dest);
  128.   cwprintf(NULL, "%4x %-5s%-9s",(int)axp,axp->interface->name,tmp);
  129.   cwputchar(NULL, axp->rejsent ? 'R' : ' ');
  130.   cwputchar(NULL, axp->remotebusy ? 'B' : ' ');
  131.   cwprintf(NULL, " %4d %4d",axp->vs,axp->vr);
  132.   cwprintf(NULL, " %02u/%02u %u",axp->unack,axp->maxframe,axp->proto);
  133.   cwprintf(NULL, " %02u/%02u",axp->retries,axp->n2);
  134.   cwprintf(NULL, " %s\r\n",ax25states[axp->state]);
  135.  
  136.   cwprintf(NULL, "T1: ");
  137.   if(run_timer(&axp->t1))
  138.     cwprintf(NULL, "%lu",(axp->t1.start - axp->t1.count) * MSPTICK);
  139.   else
  140.     cwprintf(NULL, "stop");
  141.   cwprintf(NULL, "/%lu ms; ",axp->t1.start * MSPTICK);
  142.  
  143.   cwprintf(NULL, "T2: ");
  144.   if(run_timer(&axp->t2))
  145.     cwprintf(NULL, "%lu",(axp->t2.start - axp->t2.count) * MSPTICK);
  146.   else
  147.     cwprintf(NULL, "stop");
  148.   cwprintf(NULL, "/%lu ms; ",axp->t2.start * MSPTICK);
  149.  
  150.   cwprintf(NULL, "T3: ");
  151.   if(run_timer(&axp->t3))
  152.     cwprintf(NULL, "%lu",(axp->t3.start - axp->t3.count) * MSPTICK);
  153.   else
  154.     cwprintf(NULL, "stop");
  155.   cwprintf(NULL, "/%lu ms; ",axp->t3.start * MSPTICK);
  156.  
  157.   cwprintf(NULL, "T4: ");
  158.   if(run_timer(&axp->t4))
  159.     cwprintf(NULL, "%lu",(axp->t4.start - axp->t4.count) * MSPTICK);
  160.   else
  161.     cwprintf(NULL, "stop");
  162.   cwprintf(NULL, "/%lu ms\r\n",axp->t4.start * MSPTICK);
  163.  
  164.   if(axp->addr.ndigis == 0)
  165.     return;
  166.   cwprintf(NULL, "Digipeaters:");
  167.   for(i=0;i<axp->addr.ndigis;i++)
  168.   {
  169.     pax25(tmp,&axp->addr.digis[i]);
  170.     cwprintf(NULL, " %s",tmp);
  171.   }
  172.   cwprintf(NULL, "\r\n");
  173. }
  174.  
  175. /* Display or change our AX.25 address */
  176. static int domycall(int argc, char **argv)
  177. {
  178.   char buf[15];
  179.  
  180.   if(argc < 2)
  181.   {
  182.     pax25(buf,&mycall);
  183.     cwprintf(NULL, "%s\r\n",buf);
  184.     return 0;
  185.   }
  186.   if(setcall(&mycall,argv[1]) == -1)
  187.     return -1;
  188.   mycall.ssid |= E;
  189.   return 0;
  190. }
  191.  
  192. /* Control AX.25 digipeating */
  193. static int dodigipeat(int argc, char **argv)
  194. {
  195.   extern int digipeat;
  196.   extern int32 digisent;
  197.  
  198.   if(argc == 1)
  199.   {
  200.     cwprintf(NULL, "digipeat %s\r\n",digipeat ? "on" : "off");
  201.     cwprintf(NULL, "digipeated packets = %lu\r\n",digisent);
  202.   }
  203.   else
  204.       {
  205.     if(strcmp(argv[1],"on") == 0)
  206.       digipeat = 1;
  207.     else
  208.         digipeat = 0;
  209.   }
  210.   return(0);
  211. }
  212.  
  213. /* Set retransmission timer */
  214. static int dot1(int argc, char **argv)
  215. {
  216.   extern int16 t1init;
  217.  
  218.   if(argc == 1)
  219.   {
  220.     cwprintf(NULL, "T1 %lu ms\r\n",(long)t1init * MSPTICK);
  221.   }
  222.   else
  223.       {
  224.     t1init = (int16)(atol(argv[1]) / MSPTICK);
  225.   }
  226.   return(0);
  227. }
  228.  
  229. /* Set acknowledgement delay timer */
  230. static int dot2(int argc, char **argv)
  231. {
  232.   extern int16 t2init;
  233.  
  234.   if(argc == 1)
  235.   {
  236.     cwprintf(NULL, "T2 %lu ms\r\n",(long)t2init * MSPTICK);
  237.   }
  238.   else
  239.   {
  240.     t2init = (int16)(atol(argv[1]) / MSPTICK);
  241.   }
  242.   return(0);
  243. }
  244.  
  245. /* Set idle timer */
  246. static int dot3(int argc, char **argv)
  247. {
  248.   extern int16 t3init;
  249.  
  250.   if(argc == 1)
  251.   {
  252.     cwprintf(NULL, "T3 %lu ms\r\n",(long)t3init * MSPTICK);
  253.   }
  254.   else
  255.   {
  256.     t3init = (int16)(atol(argv[1]) / MSPTICK);
  257.   }
  258.   return(0);
  259. }
  260.  
  261. /* Set link redundancy timer */
  262. static int dot4(int argc, char **argv)
  263. {
  264.   extern int16 t4init;
  265.  
  266.   if(argc == 1)
  267.   {
  268.     cwprintf(NULL, "T4 %lu sec\r\n",((long)t4init * MSPTICK) / 1000);
  269.   }
  270.   else
  271.   {
  272.     t4init = (int16)((atol(argv[1]) * 1000) / MSPTICK);
  273.   }
  274.   return(0);
  275. }
  276.  
  277. /* Set retry limit count */
  278. static int don2(int argc, char **argv)
  279. {
  280.   extern int16 n2;
  281.  
  282.   if(argc == 1)
  283.   {
  284.     cwprintf(NULL, "Retry %u\r\n",n2);
  285.   }
  286.   else
  287.       {
  288.     n2 = (int16)(atoi(argv[1]));
  289.   }
  290.   return(0);
  291. }
  292.  
  293. /* Set maximum number of frames that will be allowed in flight */
  294. static int domaxframe(int argc, char **argv)
  295. {
  296.   extern int16 maxframe;
  297.  
  298.   if(argc == 1)
  299.   {
  300.     cwprintf(NULL, "Maxframe %u\r\n",maxframe);
  301.   }
  302.   else
  303.       {
  304.     maxframe = (int16)(atoi(argv[1]));
  305.   }
  306.   return(0);
  307. }
  308.  
  309. /* Display the last x number of stations heard in the ether */
  310. static int doheard(int argc, char **argv)
  311. {
  312.   extern struct ax25mh mhlist[];  /* the mheard table (ax25.c) */
  313.   char mhcallsign[10];            /* field for call sign dumpout */
  314.   char *mhtime;                   /* ascii time representation */
  315.   int mhcolumn = 0;               /* page column display counter */
  316.   int mh_entry;                   /* index into mhlist */
  317.  
  318.   argv = argv;
  319.  
  320.   if(argc > 1)                    /* any second arg flushes mhlist */
  321.   {
  322.     for(mh_entry = 0; mh_entry < 40; mh_entry++)
  323.       mhlist[mh_entry].mheard_time = 0;       /* 0 time = empty */
  324.     return(0);
  325.   }
  326.  
  327.   qsort((void *)mhlist, 40, sizeof(struct ax25mh), mh_compare);      
  328.  
  329.   for(mh_entry = 0; mh_entry < 40; mh_entry++)
  330.   {
  331.     if(mhlist[mh_entry].mheard_time == 0)   /* empty entry? */
  332.       break;                          /* all done now */
  333.  
  334.     if(mh_entry == 0)
  335.       cwprintf(NULL, "AX25 Stations heard.  (* = heard directly)\r\n");
  336.  
  337.     pax25(mhcallsign,&mhlist[mh_entry].mheard_call); /* dump callsign */
  338.     mhtime = ctime(&mhlist[mh_entry].mheard_time);   /* get time in ascii */
  339.     rip(mhtime);                                     /* lose the \n etc */
  340.     cwprintf(NULL, "%s %s %-11s", mhtime, mhlist[mh_entry].mheard_digi ? " " : "*", mhcallsign) ;
  341.     mhcolumn++;
  342.     if(mhcolumn == 2)
  343.     {
  344.       mhcolumn = 0;
  345.       cwprintf(NULL, "\r\n");
  346.     }
  347.   }
  348.   if(mhcolumn == 1)
  349.     cwprintf(NULL, "\r\n");
  350.   return(0);
  351. }
  352.  
  353. static int mh_compare(const void *mh1, const void *mh2)
  354. {
  355.   struct ax25mh *mheard1 = (struct ax25mh *)mh1;
  356.   struct ax25mh *mheard2 = (struct ax25mh *)mh2;
  357.  
  358.   if(mheard1->mheard_time == mheard2->mheard_time)
  359.     return(0);
  360.   if(mheard1->mheard_time > mheard2->mheard_time)
  361.     return(-1);
  362.   else
  363.       return(1);
  364. }
  365.  
  366. /* Set maximum length of I-frame data field */
  367. static int dopaclen(int argc, char **argv)
  368. {
  369.   extern int16 paclen;
  370.  
  371.   if(argc == 1)
  372.   {
  373.     cwprintf(NULL, "Paclen %u\r\n",paclen);
  374.   }
  375.   else
  376.       {
  377.     paclen = atoi(argv[1]);
  378.   }
  379.   return(0);
  380. }
  381. /* Set size of I-frame above which polls will be sent after a timeout */
  382. static int dopthresh(int argc, char **argv)
  383. {
  384.   extern int16 pthresh;
  385.  
  386.   if(argc == 1)
  387.   {
  388.     cwprintf(NULL, "Pthresh %u\r\n",pthresh);
  389.   }
  390.   else
  391.       {
  392.     pthresh = atoi(argv[1]);
  393.   }
  394.   return(0);
  395. }
  396.  
  397. /* Set high water mark on receive queue that triggers RNR */
  398. static int doaxwindow(int argc, char **argv)
  399. {
  400.   extern int16 axwindow;
  401.  
  402.   if(argc == 1)
  403.   {
  404.     cwprintf(NULL, "Axwindow %u\r\n",axwindow);
  405.   }
  406.   else
  407.       {
  408.     axwindow = atoi(argv[1]);
  409.   }
  410.   return(0);
  411. }
  412. /* End of ax25 subcommands */
  413.  
  414. /* Initiate interactive AX.25 connect to remote station */
  415. int doconnect(int argc, char **argv)
  416. {
  417.   struct ax25_addr dest;
  418.   struct ax25 addr;
  419.   struct interface *ifp;
  420.   struct session *s;
  421.   extern int16 axwindow;
  422.   int i;
  423.  
  424.   for(ifp = ifaces; ifp != NULLIF; ifp = ifp->next)
  425.     if(strcmp(argv[1],ifp->name) == 0)
  426.       break;
  427.  
  428.   if(ifp == NULLIF)
  429.   {
  430.     cwprintf(NULL, "Interface %s unknown\r\n",argv[1]);
  431.     return 1;
  432.   }
  433.  
  434.   if(strcmp(argv[1],"netrom") == 0)
  435.   {
  436.     cwprintf(NULL, "Connect on netrom interface not supported.\r\n");
  437.     return 1;
  438.  
  439.   }
  440.   setcall(&dest,argv[2]);
  441.   /* See if a session already exists */
  442.   for(s = sessions; s < &sessions[nsessions]; s++)
  443.   {
  444.     if(s->type == AX25TNC
  445.         && addreq(&s->cb.ax25_cb->addr.dest,&dest))
  446.     {
  447.       cwprintf(NULL, "Session %u to %s already exists\r\n",
  448.       s - sessions,argv[2]);
  449.       return 1;
  450.     }
  451.   }
  452.   /* Allocate a session descriptor */
  453.   if((s = newsession()) == NULLSESSION)
  454.   {
  455.     cwprintf(NULL, "Too many sessions\r\n");
  456.     return 1;
  457.   }
  458.   if((s->name = malloc((unsigned)strlen(argv[2])+1)) != NULLCHAR)
  459.     strcpy(s->name,argv[2]);
  460.   s->type = AX25TNC;
  461.   s->parse = (void(*)())ax_parse;
  462.   current = s;
  463.   /* use callsign dedicated to this interface */
  464.   memcpy(&addr.source,ifp->hwaddr,sizeof(struct ax25_addr));
  465.   setcall(&addr.dest,argv[2]);
  466.   for(i=3; i < argc; i++)
  467.     setcall(&addr.digis[i-3],argv[i]);
  468.  
  469.   addr.ndigis = i - 3;
  470.   s->cb.ax25_cb = open_ax25(&addr, axwindow, (void(*)())ax_rx, (void(*)())ax_tx, (void(*)())ax_state, ifp, (char *)s);
  471.   go(s);
  472.   return 0;
  473. }
  474.  
  475.  
  476. /* Display changes in AX.25 state */
  477. void ax_state(struct ax25_cb *axp, int old, int new)
  478. {
  479.   struct session *s;
  480.  
  481.   s = (struct session *)axp->user;
  482.  
  483.   if (current != NULLSESSION && current->type == AX25TNC && current == s)
  484.   {
  485.     /* Don't print transitions between CONNECTED and RECOVERY */
  486.     if (new != RECOVERY && !(old == RECOVERY && new == CONNECTED))
  487.       cwprintf(NULL, "%s\r\n", ax25states[new]);
  488.     if (new == DISCONNECTED)
  489.       cmdmode();
  490.   }
  491.   if (new == DISCONNECTED)
  492.   {
  493.     axp->user = NULLCHAR;
  494.     freesession(s);
  495.   }
  496. }
  497. /* Handle typed characters on AX.25 connection */
  498. void ax_parse(char *buf, int16 cnt)
  499. {
  500.   struct mbuf *bp;
  501.   register char *cp;
  502.   char c;
  503.  
  504.   if(current == NULLSESSION || current->type != AX25TNC)
  505.     return; /* "can't happen" */
  506.  
  507.   /* If recording is on, record outgoing stuff too */
  508.   if(current->record != NULLFILE)
  509.     fwrite(buf,1,cnt,current->record);
  510.  
  511.   /* Allocate buffer and start it with the PID */
  512.   bp = alloc_mbuf(cnt+1);
  513.   *bp->data = PID_NO_L3;
  514.   bp->cnt++;
  515.  
  516.   /* Copy keyboard buffer to output, stripping line feeds */
  517.   cp = bp->data + 1;
  518.   while(cnt-- != 0)
  519.   {
  520.     c = *buf++;
  521.     if(c != '\r\n')
  522.     {
  523.       *cp++ = c;
  524.       bp->cnt++;
  525.     }
  526.   }
  527.   send_ax25(current->cb.ax25_cb,bp);
  528. }
  529. /* Handle new incoming terminal sessions
  530.  * This is the default receive upcall function, used when
  531.  * someone else connects to us
  532.  */
  533. void ax_incom(register struct ax25_cb *axp, int16 cnt)
  534. {
  535.   if (ax25mbox)
  536.   {
  537.     mbx_incom(axp,cnt) ;
  538.   }
  539.   else
  540.       {
  541.     ax_session(axp,cnt) ;
  542.   }
  543.   return ;
  544.  
  545. }
  546.  
  547. /* This function sets up an ax25 chat session */
  548. void ax_session(register struct ax25_cb *axp, int16 cnt)
  549. {
  550.   struct session *s;
  551.   char remote[10];
  552.   extern char hostname[];
  553.   extern int attended;
  554.   char *cp;
  555.   time_t t;
  556.  
  557.   cnt = cnt;
  558.  
  559.   time(&t);
  560.   cp = ctime(&t);
  561.   rip(cp);
  562.  
  563.   pax25(remote,&axp->addr.dest);
  564.   if((s = newsession()) == NULLSESSION)
  565.   {
  566.     /* Out of sessions */
  567.     disc_ax25(axp);
  568.     return;
  569.   }
  570.   s->type = AX25TNC;
  571.   s->name = malloc((int16)strlen(remote)+1);
  572.   s->cb.ax25_cb = axp;
  573.   s->parse = (void(*)())ax_parse;
  574.   strcpy(s->name,remote);
  575.   axp->r_upcall = (void(*)())ax_rx;
  576.   axp->s_upcall = ax_state;
  577.   axp->t_upcall = (void(*)())ax_tx;
  578.   axp->user = (char *)s;
  579.   cwprintf(NULL, "%s : Incoming AX25 session %u from %s\r\n",cp,s - sessions,remote);
  580.   log_event(NULL,"AX25 Chatter session requested : %s",remote);
  581.   if(attended)
  582.     aprintf(axp,"Welcome %s to the %s system's 'Chat' mode.\r",remote,hostname);
  583.   else
  584.       aprintf(axp,"Sorry %s, the %s system is UNATTENDED.\r",remote,hostname);
  585. }
  586.  
  587. /* Handle incoming terminal traffic */
  588. void ax_rx(struct ax25_cb *axp, int16 cnt)
  589. {
  590.   extern int ttyflow;
  591.   register struct mbuf *bp;
  592.   register char *s;
  593.   register char *t;
  594.   char *line;
  595.  
  596.   /* Hold output if we're typing */
  597.   if(mode != CONV_MODE || current == NULLSESSION || ttyflow == 0
  598.       || current->type != AX25TNC || current->cb.ax25_cb != axp)
  599.     return;
  600.  
  601.   if((bp = recv_ax25(axp,cnt)) == NULLBUF)
  602.     return;
  603.  
  604.   /* Display received characters, translating CR's to CR/LF */
  605.   if ((line = s = malloc(len_mbuf(bp) + 1)) == NULLCHAR)
  606.   {
  607.     cwprintf(NULL, "Out of memory in AX25CMD\r\n");
  608.     free_p(bp);
  609.     return;      
  610.   }
  611.  
  612.   while (bp != NULLBUF)
  613.   {
  614.     t = bp->data;
  615.     while(bp->cnt-- > 0)
  616.     {
  617.       if (*t == '\r')
  618.         *(++t) = '\n';
  619.       *s++ = *t++;
  620.     }
  621.     bp = free_mbuf(bp);
  622.   }
  623.  
  624.   if (current->record)
  625.   {
  626.     fwrite(line, 1, s - line, current->record);
  627.     fflush(current->record);
  628.   }
  629.  
  630.   *s = '\0';
  631.   cwputs(NULL, line);
  632.   free(line);
  633. }
  634. /* Handle transmit upcalls. Used only for file uploading */
  635. void ax_tx(struct ax25_cb *axp, int16 cnt)
  636. {
  637.   register char *cp;
  638.   struct session *s;
  639.   register struct mbuf *bp;
  640.   int16 size;
  641.   int c;
  642.  
  643.   if((s = (struct session *)axp->user) == NULLSESSION
  644.       || s->upload == NULLFILE)
  645.     return;
  646.   while(cnt != 0)
  647.   {
  648.     size = min(cnt,axp->paclen+1);
  649.     if((bp = alloc_mbuf(size)) == NULLBUF)
  650.       break;
  651.     cp = bp->data;
  652.     /* Start with the PID */
  653.     *cp++ = PID_NO_L3;
  654.     bp->cnt++;
  655.  
  656.     /* Now send data characters, translating between local
  657.                      * keyboard end-of-line sequences and the (unwritten)
  658.                      * AX.25 convention, which is carriage-return only
  659.                      */
  660.     while(bp->cnt < size)
  661.     {
  662.       if((c = getc(s->upload)) == EOF)
  663.         break;
  664.       if(c == '\n')
  665.         c = '\r';
  666.       *cp++ = c;
  667.       bp->cnt++;
  668.     }       
  669.     if(bp->cnt > 1)
  670.     {
  671.       send_ax25(axp,bp);
  672.     }
  673.     else
  674.     {
  675.       /* Empty frame, don't bother sending */
  676.       free_p(bp);
  677.       break;
  678.     }
  679.     cnt -= bp->cnt;
  680.   }
  681.   if(cnt != 0)
  682.   {
  683.     /* Error or end-of-file */
  684.     fclose(s->upload);
  685.     s->upload = NULLFILE;
  686.     free(s->ufile);
  687.     s->ufile = NULLCHAR;
  688.   }
  689. }
  690.  
  691.